package com.cgs.mongo.delegate;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.mongodb.client.*;
import com.mongodb.client.model.CountOptions;
import com.mongodb.client.model.InsertOneOptions;
import com.mongodb.client.model.ReplaceOptions;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import org.bson.BsonString;
import org.bson.Document;
import org.bson.codecs.Codec;

import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.cgs.mongo.utils.WrapperUtils.*;

public class MongoCollectionWrapper {

    private final MongoCollection<Document> delegate;

    public MongoCollectionWrapper(MongoCollection<Document> delegate) {
        this.delegate = delegate;
    }

    public long count() {
        return delegate.countDocuments();
    }

    public long count(Map<String, Object> filter) throws JsonProcessingException {
        return delegate.countDocuments(asBsonDocument(filter));
    }


    public long count(Map<String, Object> filter, Map<String, Object> options) throws JsonProcessingException
            , InvocationTargetException, IllegalAccessException {

        return delegate.countDocuments(asBsonDocument(filter), populate(new CountOptions(), options));
    }

    public DistinctIterable<BsonString> distinct(String fieldName) {
        return new DistinctIterableWrapper(delegate.distinct(fieldName, BsonString.class));
    }


    public DistinctIterable<BsonString> distinct(String fieldName, Map<String, Object> filter) throws JsonProcessingException {
        return new DistinctIterableWrapper(delegate.distinct(fieldName, asBsonDocument(filter), BsonString.class));
    }

    public FindIterable<Document> find() {

        return new FindIterableWrapper(delegate.find());
    }

    public FindIterable<Document> find(Map<String, Object> filter) throws JsonProcessingException {
        return new FindIterableWrapper(delegate.find(asBsonDocument(filter)));
    }

    public AggregateIterable<Document> aggregate(List<Map<String, Object>> pipeline) throws JsonProcessingException {
        return new AggregateIterableWrapper(delegate.aggregate(asBsonDocuments(pipeline)));
    }

    public ChangeStreamIterable<Document> watch() {
        return new ChangeStreamIterableWrapper(delegate.watch());
    }

    public ChangeStreamIterable<Document> watch(List<Map<String, Object>> pipeline) throws JsonProcessingException {
        return new ChangeStreamIterableWrapper(delegate.watch(asBsonDocuments(pipeline)));
    }

    public void insert(Map<String, Object> document) {
        delegate.insertOne(new Document(document));
    }

    public void insertOne(Map<String, Object> document) {
        delegate.insertOne(new Document(document));
    }

    public void insertOne(Map<String, Object> document, boolean bypassDocumentValidation) {
        InsertOneOptions insertOneOptions = new InsertOneOptions();
        insertOneOptions.bypassDocumentValidation(bypassDocumentValidation);
        delegate.insertOne(new Document(document), insertOneOptions);
    }

    public void insertMany(List<Map<String, Object>> documents) {
        delegate.insertMany(documents.stream().map(Document::new).collect(Collectors.toList()));
    }

    public DeleteResult deleteOne(Map<String, Object> filter) throws JsonProcessingException {
        return delegate.deleteOne(asBsonDocument(filter));
    }

    public DeleteResult deleteMany(Map<String, Object> filter) throws JsonProcessingException {
        return delegate.deleteMany(asBsonDocument(filter));
    }

    public DeleteResult delete(Map<String, Object> filter, boolean justOne) throws JsonProcessingException {
        return justOne ? deleteOne(filter) : deleteMany(filter);
    }

    public DeleteResult delete(Map<String, Object> filter) throws JsonProcessingException {
        return deleteMany(filter);
    }

    public UpdateResult replaceOne(Map<String, Object> filter, Map<String, Object> replacement) throws JsonProcessingException {
        return delegate.replaceOne(asBsonDocument(filter), new Document(replacement));
    }

    public UpdateResult replaceOne(Map<String, Object> filter, Map<String, Object> replacement, boolean upsert) throws JsonProcessingException {
        ReplaceOptions replaceOptions = new ReplaceOptions();
        replaceOptions.upsert(upsert);
        return delegate.replaceOne(asBsonDocument(filter), new Document(replacement), replaceOptions);
    }

    public UpdateResult update(Map<String, Object> filter, Map<String, Object> update) throws JsonProcessingException {
        return update(filter, update, Collections.EMPTY_MAP);
    }

    public UpdateResult update(Map<String, Object> filter, Map<String, Object> update, Map<String, Object> options) throws JsonProcessingException {

        return getBoolean(options, "justOne", false) ? updateOne(filter, update, options) :
                updateMany(filter, update, options);
    }

    public UpdateResult updateOne(Map<String, Object> filter, Map<String, Object> update) throws JsonProcessingException {
        return delegate.updateOne(asBsonDocument(filter), asBsonDocument(update));
    }

    public UpdateResult updateOne(Map<String, Object> filter, Map<String, Object> update, Map<String, Object> options) throws JsonProcessingException {
        UpdateOptions updateOptions = new UpdateOptions();
        updateOptions.upsert(getBoolean(options, "upsert", false));
        return delegate.updateOne(asBsonDocument(filter), asBsonDocument(update), updateOptions);
    }

    public UpdateResult updateMany(Map<String, Object> filter, Map<String, Object> update) throws JsonProcessingException {
        return delegate.updateMany(asBsonDocument(filter), asBsonDocument(update));
    }

    public UpdateResult updateMany(Map<String, Object> filter, Map<String, Object> update, Map<String, Object> options) throws JsonProcessingException {
        UpdateOptions updateOptions = new UpdateOptions();
        updateOptions.upsert(getBoolean(options, "upsert", false));
        updateOptions.bypassDocumentValidation(true);
        return delegate.updateMany(asBsonDocument(filter), asBsonDocument(update), updateOptions);
    }

    public void drop() {
        delegate.drop();
    }
}
